{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "## 模块\n",
    "\n",
    "\n",
    "在计算机程序的开发过程中，随着程序代码越写越多，在一个文件里代码就会越来越长，越来越不容易维护。\n",
    "\n",
    "为了编写可维护的代码，我们把很多函数分组，分别放到不同的文件里，这样，每个文件包含的代码就相对较少，很多编程语言都采用这种组织代码的方式。在Python中，一个.py文件就称之为一个模块（Module）。\n",
    "\n",
    "使用模块有什么好处？\n",
    "\n",
    "最大的好处是大大提高了代码的可维护性。其次，编写代码不必从零开始。当一个模块编写完毕，就可以被其他地方引用。我们在编写程序的时候，也经常引用其他模块，包括Python内置的模块和来自第三方的模块。\n",
    "\n",
    "使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中，因此，我们自己在编写模块时，不必考虑名字会与其他模块冲突。但是也要注意，尽量不要与内置函数名字冲突。点[这里](http://docs.python.org/3/library/functions.html)查看Python的所有内置函数。\n",
    "\n",
    "你也许还想到，如果不同的人编写的模块名相同怎么办？为了避免模块名冲突，Python又引入了按目录来组织模块的方法，称为包（Package）。\n",
    "\n",
    "举个例子，一个`abc.py`的文件就是一个名字叫`abc`的模块，一个`xyz.py`的文件就是一个名字叫`xyz`的模块。\n",
    "\n",
    "现在，假设我们的`abc`和`xyz`这两个模块名字与其他模块冲突了，于是我们可以通过包来组织模块，避免冲突。方法是选择一个顶层包名，比如`mycompany`，按照如下目录存放：\n",
    "\n",
    "```\n",
    "mycompany\n",
    "├─ __init__.py\n",
    "├─ abc.py\n",
    "└─ xyz.py\n",
    "```\n",
    "\n",
    "引入了包以后，只要顶层的包名不与别人冲突，那所有模块都不会与别人冲突。现在，`abc.py`模块的名字就变成了`mycompany.abc`，类似的，`xyz.py`的模块名变成了`mycompany.xyz`。\n",
    "\n",
    "请注意，每一个包目录下面都会有一个`__init__.py`的文件，这个文件是必须存在的，否则，Python就把这个目录当成普通目录，而不是一个包。`__init__.py`可以是空文件，也可以有Python代码，因为`__init__.py`本身就是一个模块，而它的模块名就是`mycompany`。\n",
    "\n",
    "类似的，可以有多级目录，组成多级层次的包结构。比如如下的目录结构：\n",
    "\n",
    "```\n",
    "mycompany\n",
    " ├─ web\n",
    " │  ├─ __init__.py\n",
    " │  ├─ utils.py\n",
    " │  └─ www.py\n",
    " ├─ __init__.py\n",
    " ├─ abc.py\n",
    " └─ utils.py\n",
    "```\n",
    "\n",
    "文件`www.py`的模块名就是`mycompany.web.www`，两个文件`utils.py`的模块名分别是`mycompany.utils`和`mycompany.web.utils`。\n",
    "\n",
    "自己创建模块时要注意命名，不能和Python自带的模块名称冲突。例如，系统自带了sys模块，自己的模块就不可命名为sys.py，否则将无法导入系统自带的sys模块。\n",
    "\n",
    "`mycompany.web`也是一个模块，请指出该模块对应的.py文件。\n",
    "\n",
    "### 总结\n",
    "\n",
    "模块是一组Python代码的集合，可以使用其他模块，也可以被其他模块使用。\n",
    "\n",
    "创建自己的模块时，要注意：\n",
    "\n",
    "-   模块名要遵循Python变量命名规范，不要使用中文、特殊字符；\n",
    "-   模块名不要和系统模块名冲突，最好先查看系统是否已存在该模块，检查方法是在Python交互环境执行`import abc`，若成功则说明系统存在此模块。"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 使用模块\n",
    "\n",
    "Python本身就内置了很多非常有用的模块，只要安装完毕，这些模块就可以立刻使用。\n",
    "\n",
    "我们以内建的`sys`模块为例，编写一个`hello`的模块：\n",
    "\n",
    "```\n",
    "#!/usr/bin/env python3\n",
    "# -*- coding: utf-8 -*-\n",
    "\n",
    "' a test module '\n",
    "\n",
    "__author__ = 'Michael Liao'\n",
    "\n",
    "import sys\n",
    "\n",
    "def test():\n",
    "    args = sys.argv\n",
    "    if len(args)==1:\n",
    "        print('Hello, world!')\n",
    "    elif len(args)==2:\n",
    "        print('Hello, %s!' % args[1])\n",
    "    else:\n",
    "        print('Too many arguments!')\n",
    "\n",
    "if __name__=='__main__':\n",
    "    test()\n",
    "```\n",
    "\n",
    "第1行和第2行是标准注释，第1行注释可以让这个`hello.py`文件直接在Unix/Linux/Mac上运行，第2行注释表示.py文件本身使用标准UTF-8编码；\n",
    "\n",
    "第4行是一个字符串，表示模块的文档注释，任何模块代码的第一个字符串都被视为模块的文档注释；\n",
    "\n",
    "第6行使用`__author__`变量把作者写进去，这样当你公开源代码后别人就可以瞻仰你的大名；\n",
    "\n",
    "以上就是Python模块的标准文件模板，当然也可以全部删掉不写，但是，按标准办事肯定没错。\n",
    "\n",
    "后面开始就是真正的代码部分。\n",
    "\n",
    "你可能注意到了，使用`sys`模块的第一步，就是导入该模块：\n",
    "\n",
    "```\n",
    "import sys\n",
    "```\n",
    "\n",
    "导入`sys`模块后，我们就有了变量`sys`指向该模块，利用`sys`这个变量，就可以访问`sys`模块的所有功能。\n",
    "\n",
    "`sys`模块有一个`argv`变量，用list存储了命令行的所有参数。`argv`至少有一个元素，因为第一个参数永远是该.py文件的名称，例如：\n",
    "\n",
    "运行`python3 hello.py`获得的`sys.argv`就是`['hello.py']`；\n",
    "\n",
    "运行`python3 hello.py Michael`获得的`sys.argv`就是`['hello.py', 'Michael']`。\n",
    "\n",
    "最后，注意到这两行代码：\n",
    "\n",
    "```\n",
    "if __name__=='__main__':\n",
    "    test()\n",
    "```\n",
    "\n",
    "当我们在命令行运行`hello`模块文件时，Python解释器把一个特殊变量`__name__`置为`__main__`，而如果在其他地方导入该`hello`模块时，`if`判断将失败，因此，这种`if`测试可以让一个模块通过命令行运行时执行一些额外的代码，最常见的就是运行测试。\n",
    "\n",
    "我们可以用命令行运行`hello.py`看看效果：\n",
    "\n",
    "```\n",
    "$ python3 hello.py\n",
    "Hello, world!\n",
    "$ python hello.py Michael\n",
    "Hello, Michael!\n",
    "```\n",
    "\n",
    "如果启动Python交互环境，再导入`hello`模块：\n",
    "\n",
    "```\n",
    "$ python3\n",
    "Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 23 2015, 02:52:03)\n",
    "[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin\n",
    "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n",
    ">>> import hello\n",
    ">>>\n",
    "```\n",
    "\n",
    "导入时，没有打印`Hello, word!`，因为没有执行`test()`函数。\n",
    "\n",
    "调用`hello.test()`时，才能打印出`Hello, word!`：\n",
    "\n",
    "```\n",
    ">>> hello.test()\n",
    "Hello, world!\n",
    "```\n",
    "\n",
    "### 作用域\n",
    "\n",
    "在一个模块中，我们可能会定义很多函数和变量，但有的函数和变量我们希望给别人使用，有的函数和变量我们希望仅仅在模块内部使用。在Python中，是通过`_`前缀来实现的。\n",
    "\n",
    "正常的函数和变量名是公开的（public），可以被直接引用，比如：`abc`，`x123`，`PI`等；\n",
    "\n",
    "类似`__xxx__`这样的变量是特殊变量，可以被直接引用，但是有特殊用途，比如上面的`__author__`，`__name__`就是特殊变量，`hello`模块定义的文档注释也可以用特殊变量`__doc__`访问，我们自己的变量一般不要用这种变量名；\n",
    "\n",
    "类似`_xxx`和`__xxx`这样的函数或变量就是非公开的（private），不应该被直接引用，比如`_abc`，`__abc`等；\n",
    "\n",
    "之所以我们说，private函数和变量“不应该”被直接引用，而不是“不能”被直接引用，是因为Python并没有一种方法可以完全限制访问private函数或变量，但是，从编程习惯上不应该引用private函数或变量。\n",
    "\n",
    "private函数或变量不应该被别人引用，那它们有什么用呢？请看例子：\n",
    "\n",
    "```\n",
    "def _private_1(name):\n",
    "    return 'Hello, %s' % name\n",
    "\n",
    "def _private_2(name):\n",
    "    return 'Hi, %s' % name\n",
    "\n",
    "def greeting(name):\n",
    "    if len(name) > 3:\n",
    "        return _private_1(name)\n",
    "    else:\n",
    "        return _private_2(name)\n",
    "```\n",
    "\n",
    "我们在模块里公开`greeting()`函数，而把内部逻辑用private函数隐藏起来了，这样，调用`greeting()`函数不用关心内部的private函数细节，这也是一种非常有用的代码封装和抽象的方法，即：\n",
    "\n",
    "外部不需要引用的函数全部定义成private，只有外部需要引用的函数才定义为public。"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 安装第三方模块\n",
    "\n",
    "在Python中，安装第三方模块，是通过包管理工具pip完成的。\n",
    "\n",
    "如果你正在使用Mac或Linux，安装pip本身这个步骤就可以跳过了。\n",
    "\n",
    "如果你正在使用Windows，请参考[安装Python](https://www.liaoxuefeng.com/wiki/1016959663602400/1016959856222624)一节的内容，确保安装时勾选了`pip`和`Add python.exe to Path`。\n",
    "\n",
    "在命令提示符窗口下尝试运行`pip`，如果Windows提示未找到命令，可以重新运行安装程序添加`pip`。\n",
    "\n",
    "注意：Mac或Linux上有可能并存Python 3.x和Python 2.x，因此对应的pip命令是`pip3`。\n",
    "\n",
    "例如，我们要安装一个第三方库——Python Imaging Library，这是Python下非常强大的处理图像的工具库。不过，PIL目前只支持到Python 2.7，并且有年头没有更新了，因此，基于PIL的Pillow项目开发非常活跃，并且支持最新的Python 3。\n",
    "\n",
    "一般来说，第三方库都会在Python官方的[pypi.python.org](https://pypi.python.org/)网站注册，要安装一个第三方库，必须先知道该库的名称，可以在官网或者pypi上搜索，比如Pillow的名称叫[Pillow](https://pypi.python.org/pypi/Pillow/)，因此，安装Pillow的命令就是：\n",
    "\n",
    "```\n",
    "pip install Pillow\n",
    "```\n",
    "\n",
    "耐心等待下载并安装后，就可以使用Pillow了。\n",
    "\n",
    "![且慢](https://www.liaoxuefeng.com/files/attachments/1183096336546240/l)\n",
    "\n",
    "### 安装常用模块\n",
    "\n",
    "在使用Python时，我们经常需要用到很多第三方库，例如，上面提到的Pillow，以及MySQL驱动程序，Web框架Flask，科学计算Numpy等。用pip一个一个安装费时费力，还需要考虑兼容性。我们推荐直接使用[Anaconda](https://www.anaconda.com/)，这是一个基于Python的数据处理和科学计算平台，它已经内置了许多非常有用的第三方库，我们装上Anaconda，就相当于把数十个第三方模块自动安装好了，非常简单易用。\n",
    "\n",
    "可以从[Anaconda官网](https://www.anaconda.com/download/)下载GUI安装包，安装包有500~600M，所以需要耐心等待下载。下载后直接安装，Anaconda会把系统Path中的python指向自己自带的Python，并且，Anaconda安装的第三方模块会安装在Anaconda自己的路径下，不影响系统已安装的Python目录。\n",
    "\n",
    "安装好Anaconda后，重新打开命令行窗口，输入python，可以看到Anaconda的信息：\n",
    "\n",
    "```\n",
    "┌────────────────────────────────────────────────────────┐\n",
    "│Command Prompt - python                           - □ x │\n",
    "├────────────────────────────────────────────────────────┤\n",
    "│Microsoft Windows [Version 10.0.0]                      │\n",
    "│(c) 2015 Microsoft Corporation. All rights reserved.    │\n",
    "│                                                        │\n",
    "│C:\\> python                                             │\n",
    "│Python 3.6.3 |Anaconda, Inc.| ... on win32              │\n",
    "│Type \"help\", ... for more information.                  │\n",
    "│>>> import numpy                                        │\n",
    "│>>> _                                                   │\n",
    "│                                                        │\n",
    "│                                                        │\n",
    "│                                                        │\n",
    "└────────────────────────────────────────────────────────┘\n",
    "```\n",
    "\n",
    "可以尝试直接`import numpy`等已安装的第三方模块。\n",
    "\n",
    "### 模块搜索路径\n",
    "\n",
    "当我们试图加载一个模块时，Python会在指定的路径下搜索对应的.py文件，如果找不到，就会报错：\n",
    "\n",
    "```\n",
    ">>> import mymodule\n",
    "Traceback (most recent call last):\n",
    "  File \"<stdin>\", line 1, in <module>\n",
    "ImportError: No module named mymodule\n",
    "```\n",
    "\n",
    "默认情况下，Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块，搜索路径存放在`sys`模块的`path`变量中：\n",
    "\n",
    "```\n",
    ">>> import sys\n",
    ">>> sys.path\n",
    "['', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', ..., '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages']\n",
    "```\n",
    "\n",
    "如果我们要添加自己的搜索目录，有两种方法：\n",
    "\n",
    "一是直接修改`sys.path`，添加要搜索的目录：\n",
    "\n",
    "```\n",
    ">>> import sys\n",
    ">>> sys.path.append('/Users/michael/my_py_scripts')\n",
    "```\n",
    "\n",
    "这种方法是在运行时修改，运行结束后失效。\n",
    "\n",
    "第二种方法是设置环境变量`PYTHONPATH`，该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径，Python自己本身的搜索路径不受影响。"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}